梦想不会自己发光,真正闪耀的是那个为梦狂奔的你。献给知行的孩子们!(Eric.He著)
队列是「先进先出 (First In First Out, FIFO)」的线性数据结构,核心特点:只能从队尾插入元素(入队 enqueue)、只能从队头删除元素(出队 dequeue)、队头 / 队尾可分别查看,注意:双端队列可突破限制,实现头尾操作元素。本文将详细讲解队列在迷宫求解、任务排队 、滑动窗口、约瑟夫环中的应用,内容由浅入深,所有代码可直接编译运行。
队列的两种实现方式:
学习经典应用场景前,请根据上面的教程封装好自定义队列,所有场景实例直接复用
迷宫求解是 BFS 的经典实战场景,核心需求:从迷宫起点到终点,找到「最少步数」的路径(DFS 能找路径但无法保证最短,BFS 是最优解)。
广度优先搜索(BFS)是队列的标志性应用,核心解决「层级遍历、最短路径」类问题,
问题描述:
算法解析:
BFS 的「层级特性」天然适配「最短路径」—— 每一层对应「走了 n 步」,第一次到达终点时的层数就是最少步数:
#include <iostream>
#include"ArrayQueue.h"
using namespace std;
// 迷宫坐标结构体(存储x,y坐标+步数)
struct Pos{
int x;
int y;
int step;
};
// 迷宫最短路径求解(BFS+队列)
// maze:迷宫数组,rows/cols:迷宫行列数,sx/sy:起点,ex/ey:终点
int mazeShortestPath(int maze[][5], int rows, int cols, int sx, int sy, int ex, int ey) {
// 边界校验
if (sx < 0 || sx >= rows || sy < 0 || sy >= cols ||
ex < 0 || ex >= rows || ey < 0 || ey >= cols ||
maze[sx][sy] == 1 || maze[ex][ey] == 1) {
return -1; // 起点/终点非法
}
if (sx == ex && sy == ey) {
return 0; // 起点即终点
}
ArrayQueue<Pos> queue;
int visited[500][500] = {{0}}; // 标记是否访问过
int dirs[4][2] = {{-1,0},{1,0},{0,-1},{0,1}}; // 上下左右
// 起点入队
queue.enQueue({sx, sy, 0});
visited[sx][sy] = 1;
while (!queue.isEmpty()) {
Pos cur = queue.deQueue();
// 遍历四个方向
for (int i = 0; i < 4; i++) {
int nx = cur.x + dirs[i][0];
int ny = cur.y + dirs[i][1];
// 到达终点,返回步数+1
if (nx == ex && ny == ey) {
return cur.step + 1;
}
// 新坐标合法且可走、未访问
if (nx >= 0 && nx < rows && ny >= 0 && ny < cols && maze[nx][ny] == 0 && !visited[nx][ny]) {
visited[nx][ny] = 1;
queue.enQueue({nx, ny, cur.step + 1});
}
}
}
return -1; // 无路径
}
// 测试案例
int main() {
// 迷宫示例:5行5列,0可走,1墙
int maze[5][5] = {
{0, 1, 0, 0, 0},
{0, 1, 0, 1, 0},
{0, 0, 0, 1, 0},
{1, 1, 0, 1, 0},
{0, 0, 0, 0, 0}
};
int sx = 0, sy = 0; // 起点(0,0)
int ex = 4, ey = 4; // 终点(4,4)
int minStep = mazeShortestPath(maze, 5, 5, sx, sy, ex, ey);
if (minStep == -1) {
printf("迷宫无有效路径!\n");
} else {
printf("迷宫从(%d,%d)到(%d,%d)的最短步数:%d\n", sx, sy, ex, ey, minStep); // 预期:8
}
return 0;
}
迷宫从(0,0)到(4,4)的最短步数:8
队列是「任务调度、消息通信」的核心数据结构。
问题描述:
算法解析:
利用队列「先进先出」的特性,保证任务 / 消息按「提交顺序」处理,避免插队,核心逻辑:
#include <iostream>
using namespace std;
// 打印任务结构体
struct PrintTask{
int taskId; // 任务ID
char content[50]; // 打印内容
};
// 模拟打印机任务队列
void printerQueueSim() {
ArrayQueue<PrintTask> queue;
// 提交3个打印任务
PrintTask task1 = {1, "简历.pdf"};
PrintTask task2 = {2, "论文.docx"};
PrintTask task3 = {3, "报表.xlsx"};
queue.enQueue(task1);
queue.enQueue(task2);
queue.enQueue(task3);
printf("打印机开始处理任务(先进先出):\n");
while (!queue.isEmpty()) {
PrintTask taskAddr;
taskAddr = queue.deQueue();
PrintTask *cur = &taskAddr;
printf("处理任务%d:打印 %s\n", cur->taskId, cur->content);
}
printf("所有打印任务处理完成!\n");
}
// 测试案例
int main() {
printerQueueSim();
return 0;
}
打印机开始处理任务(先进先出):
处理任务1:打印 简历.pdf
处理任务2:打印 论文.docx
处理任务3:打印 报表.xlsx
所有打印任务处理完成!
滑动窗口最大值:给你一个整数数组,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧,每向右滑动一次取窗口中最大值。
问题描述:
算法解析:
维护一个「单调递减队列」,队列中存储数组下标,保证队头是当前窗口的最大值下标
#include <iostream>
#include <vector>
#include"LinkedDeque.h"
using namespace std;
// 滑动窗口最大值:双端队列解法
void maxSlidingWindow(int nums[], int n, int k) {
vector<int> result; // 存储每个窗口的最大值
LinkedDeque<int> dq; // 双端队列,存储nums的下标,而非值
for (int i = 0; i < n; ++i) {
// 1. 移除超出窗口范围的队头元素(窗口左边界:i - k + 1)
while(!dq.isEmpty() && dq.getHead() < i - k + 1) {
dq.popHead();
}
// 2. 移除队列尾部所有小于当前元素的下标(维护递减队列)
while(!dq.isEmpty() && nums[dq.getTail()] < nums[i]) {
dq.popTail();
}
// 3. 将当前元素下标加入队列尾部
dq.pushTail(i);
// 4. 当遍历到第k-1个元素后,开始记录每个窗口的最大值
if (i >= k - 1) {
result.push_back(nums[dq.getHead()]);
}
}
// 输出结果:3 3 5 5 6 7
cout << "滑动窗口最大值结果:";
for (int num : result) {
cout << num << " ";
}
cout << endl;
}
// 测试案例
int main() {
int nums[] = {1,3,-1,-3,5,3,6,7};
int n = sizeof(nums)/sizeof(nums[0]);
int k = 3;
maxSlidingWindow(nums, n, k); // 输出:3 3 5 5 6 7
return 0;
}
滑动窗口最大值结果:3 3 5 5 6 7
#include <iostream>
#include <vector>
#include"LinkedDeque.h"
using namespace std;
// 滑动窗口最小值:双端队列解法
void minSlidingWindow(int nums[], int n, int k) {
vector result; // 存储每个窗口的最小值
LinkedDeque dq; // 双端队列,存储nums的下标,而非值
for (int i = 0; i < n; ++i) {
// 1. 移除超出窗口范围的队头元素(窗口左边界:i - k + 1)
while(!dq.isEmpty() && dq.getHead() < i - k + 1) {
dq.popHead();
}
// 2. 移除队列尾部所有大于当前元素的下标(维护递减队列)
while(!dq.isEmpty() && nums[dq.getTail()] > nums[i]) {
dq.popTail();
}
// 3. 将当前元素下标加入队列尾部
dq.pushTail(i);
// 4. 当遍历到第k-1个元素后,开始记录每个窗口的最小值
if (i >= k - 1) {
result.push_back(nums[dq.getHead()]);
}
}
// 输出结果:3 3 5 5 6 7
cout << "滑动窗口最小值结果:";
for (int num : result) {
cout << num << " ";
}
cout << endl;
}
// 测试案例
int main() {
int nums[] = {1,3,-1,-3,5,3,6,7};
int n = sizeof(nums)/sizeof(nums[0]);
int k = 3;
minSlidingWindow(nums, n, k); // 输出:-1 -3 -3 -3 3 3
return 0;
}
滑动窗口最小值结果:-1 -3 -3 -3 3 3
约瑟夫环是经典的循环队列问题:n 个人围成一圈,从第 1 个人开始报数,报到 m 的人出列,接着下一个人继续报数,直到最后只剩 1 个人,求最后存活的人的编号。
问题描述:
算法解析:
利用队列的「循环特性」模拟报数过程:
#include <iostream>
#include"ArrayQueue.h"
using namespace std;
// 约瑟夫环问题(队列模拟)
int josephus(int n, int m) {
if (n <= 0 || m <= 0) return -1;
ArrayQueue<int> queue;
// 1~n入队
for (int i = 1; i <= n; i++) {
queue.enQueue(i);
}
int count = 0; // 报数计数器
while (queue.getSize() > 1) {
int val = queue.deQueue();// 队头出队
count++;
// 报数未到m,重新入队
if (count != m) {
queue.enQueue(val);
} else {
count = 0; // 重置计数器
printf("出列的人:%d\n", val);
}
}
// 队中剩余的最后一个元素
int survivor = queue.getHead();
return survivor;
}
// 测试案例
int main() {
int n = 5, m = 3; // 5人报数,报到3出列
int survivor = josephus(n, m);
printf("约瑟夫环(n=%d,m=%d)最后存活的人:%d\n", n, m, survivor); // 预期:4
return 0;
}
出列的人:3
出列的人:1
出列的人:5
出列的人:2
约瑟夫环(n=5,m=3)最后存活的人:4